#include "codegen.hpp"


namespace codegen
{

using std::ios;


BinaryCodeGeneratorPass::BinaryCodeGeneratorPass(ostream& errors, ostream& out, int start)
    : Pass(errors), output(out)
{
  emit_word(start);
}


void BinaryCodeGeneratorPass::visit(Program& program)
{
  TextSectionList* textSections = program.textSections;
  DataSectionList* dataSections = program.dataSections;

  TextSectionList::iterator text_it;
  DataSectionList::iterator data_it;

  for (text_it = textSections->begin(); text_it != textSections->end(); ++text_it) {
    TextSection& text = **text_it;
    
    for (uint i = 0; i < text.instructions->size(); ++i)
      emit_instruction(*text.instructions->at(i));
  }
  
  for (data_it = dataSections->begin(); data_it != dataSections->end(); ++data_it) {
    DataSection& data = **data_it;

    for (uint i = 0; i < data.directives->size(); ++i)
      emit_data(*data.directives->at(i));
  }
}


void BinaryCodeGeneratorPass::emit_instruction(Instruction& instr)
{
  uint code = 0;
  uint opcode = risco::instruction_code(instr.opcode);
  uint payload = operand_payload(instr);

  code = (opcode << 25) | ((instr.apsBit ? 1 : 0) << 24) | payload;

  emit_word(code);
}


uint BinaryCodeGeneratorPass::operand_payload(Instruction& instr)
{
  uint payload = 0;
  uint op1 = instr.op1->arg.value;
  uint op2 = instr.op2->arg.value;
  uint op3 = instr.op3->arg.value;

  payload |= (op1 << 17);             // r_dst

  switch (instr.operand_format) {
    case 1:
      payload |= (op2 << 12)          // r_ft1
              |  (op3 << 6);          // r_ft2
      break;

    case 2:
      payload |= (op2 << 12)          // r_ft1
              |  (1 << 11)            // ss2
              |  (op3 & ~(~0 << 11)); // op3
      break;

    case 3:
      payload |= (1 << 22)            // f0
              |  (op3 & ~(~0 << 17)); // op3
      break;

    case 4:
      payload |= (1 << 23)            // f1
              |  (op3 >> 15);
      break;

    case 5:
      payload |= (3 << 22)            // f1, f0
              |  (op3 & ~(~0 << 17)); // op3
      break;

    default:
      break;
  }

  return payload;
}


void BinaryCodeGeneratorPass::emit_data(Directive& d)
{
  switch (d.dirType) {
    case Tascii:
      output << *d.arg.str;
      break;

    case Tasciiz:
      output << *d.arg.str << '\0';
      break;

    case Tresw:
      for (uint i = 0; i < 4*d.arg.size; ++i)
        emit_byte(0);
      break;

    case Tresb:
      for (uint i = 0; i < d.arg.size; ++i)
        emit_byte(0);
      break;

    case Tword:
      for (uint i = 0; i < d.arg.intList->size(); ++i)
        emit_word(d.arg.intList->at(i));
      break;

    case Tbyte:
      for (uint i = 0; i < d.arg.intList->size(); ++i)
        emit_byte(d.arg.intList->at(i));
      break;
  }
}


void BinaryCodeGeneratorPass::emit_byte(uint n)
{
  output.put(static_cast<char>(n));
}


void BinaryCodeGeneratorPass::emit_word(uint n)
{
  output.write((const char*)&n, sizeof(uint));
}


}
